/**
1. ECMA-262 将对象定义为一组属性的无序集合。严格来说，这意味着对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识，这个名称映射到一个值。
2. ECMA-262 使用一些内部特性来描述属性的特征。为了将某个特性标识为内部特性，规范会用两个中括号把特性的名称括起来，比如[[Enumerable]]。
 */

/**
 * 数据属性
 * 1. 数据属性有 4 个特性描述它们的行为:
 * 1.1 [[Configurable]]：表示属性是否可以通过 delete 删除并重新定义，是否可以修改它的特性，以及是否可以把它改为访问器属性。默认情况下，所有直接定义在对象上的属性的这个特性都是 true。
 * 1.2 [[Enumerable]]：表示属性是否可以通过 for-in 循环返回。默认情况下，所有直接定义在对象上的属性的这个特性都是 true 。
 * 1.3 [[Writable]]：表示属性的值是否可以被修改。默认情况下，所有直接定义在对象上的属性的这个特性都是 true。
 * 1.4 [[Value]]：包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为 undefined。
 * 2. 要修改属性的默认特性，就必须使用 Object.defineProperty()方法。这个方法接收 3 个参数：要给其添加属性的对象、属性的名称和一个描述符对象。最后一个参数，即描述符对象上的属性可以包含：configurable、enumerable、writable 和 value，跟相关特性的名称一一对应。
 * 3. 一个属性被定义为不可配置之后，就不能再变回可配置的了。
 * 4. 在调用 Object.defineProperty()时，configurable、enumerable 和 writable 的值如果不指定，则都默认为 false
 */
let p_name = Symbol('name');
let person = {};
Object.defineProperty(person, p_name, {
  value: 'Nicholas',
  writable: false,
  configurable: false,
  enumerable: true
});
console.log(person[p_name]); // Nicholas
person[p_name] = 'Greg';
console.log(person[p_name]); // Nicholas
delete person[p_name];
console.log(person[p_name]); // Nicholas
Object.defineProperty(person, p_name, {
  // configurable: true // TypeError: Cannot redefine property: Symbol(name)
});
delete person[p_name];
console.log(person[p_name]);

/**
 *  访问器属性
 * 1. 访问器属性有 4 个特性描述它们的行为:
 * 1.1 [[Configurable]]：表示属性是否可以通过 delete 删除并重新定义，是否可以修改它的特性，以及是否可以把它改为数据属性。默认情况下，所有直接定义在对象上的属性的这个特性都是 true。
 * 1.2 [[Enumerable]]：表示属性是否可以通过 for-in 循环返回。默认情况下，所有直接定义在对象上的属性的这个特性都是 true。
 * 1.3 [[Get]]：获取函数，在读取属性时调用。默认值为 undefined。
 * 1.4 [[Set]]：设置函数，在写入属性时调用。默认值为 undefined。
 * 2. 访问器属性是不能直接定义的，必须使用 Object.defineProperty()。
 * 3. 在严格模式下，尝试写入只定义了获取函数的属性会抛出错误。类似地，只有一个设置函数的属性是不能读取的，非严格模式下读取会返回 undefined，严格模式下会抛出错误。
 */

// 定义一个对象，包含伪私有成员 year_和公共成员 edition
let year_ = Symbol('year_');
let edition = Symbol('edition');
let book = {
  [year_]: 2017,
  [edition]: 1
};
Object.defineProperty(book, 'year', {
  get() {
    return this.year_;
  },
  set(newValue) {
    if (newValue > 2017) {
      this[year_] = newValue;
      this[edition] += newValue - 2017;
      this;
    }
  }
});
book.year = 2018;
console.log(book[year_], book[edition], book.year);
console.log(book[edition]); // 2
